<!DOCTYPE html>
<html>
  <head><meta name="generator" content="Hexo 3.8.0">
  <meta http-equiv="content-type" content="text/html; charset=utf-8">
  <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport">
  <meta name="description" content="fengzhaoyang&#39;s blog">
  <meta name="keyword" content="hexo-theme, vuejs">
  
  <link rel="shortcut icon" href="/css/images/logo.png">
  
  <title>
    
    redis分布式缓存 | fzy-blog
    
  </title>
  <link href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
  <link href="//cdnjs.cloudflare.com/ajax/libs/nprogress/0.2.0/nprogress.min.css" rel="stylesheet">
  <link href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/tomorrow.min.css" rel="stylesheet">
  <link rel="stylesheet" href="/css/style.css">
  
  <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
  <script src="//cdnjs.cloudflare.com/ajax/libs/geopattern/1.2.3/js/geopattern.min.js"></script>
  <script src="//cdnjs.cloudflare.com/ajax/libs/nprogress/0.2.0/nprogress.min.js"></script>
  
  <script src="/js/qrious.js"></script>
  
  
  
  
    <!-- MathJax support START -->
    <script type="text/x-mathjax-config">
      MathJax.Hub.Config({
        tex2jax: {
          inlineMath: [ ['$','$'], ["\\(","\\)"]  ],
          processEscapes: true,
          skipTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code']
        }
      });
    </script>

    <script type="text/x-mathjax-config">
      MathJax.Hub.Queue(function() {
        var all = MathJax.Hub.getAllJax(), i;
        for (i=0; i < all.length; i += 1) {
          all[i].SourceElement().parentNode.className += ' has-jax';
        }
      });
    </script>
    <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
    <!-- MathJax support END -->
  


</head>
<div class="wechat-share">
  <img src="/css/images/logo.png">
</div>
  <body>
    <header class="header fixed-header">
  <div class="header-container">
    <a class="home-link" href="/">
      <div class="logo"></div>
      <span>fzy-blog</span>
    </a>
    <ul class="right-list">
      
        <li class="list-item">
          
            <a href="/" class="item-link">Home</a>
          
        </li>
      
        <li class="list-item">
          
            <a href="/tags/" class="item-link">Tags</a>
          
        </li>
      
        <li class="list-item">
          
            <a href="/archives/" class="item-link">Archives</a>
          
        </li>
      
        <li class="list-item">
          
            <a href="/project/" class="item-link">Projects</a>
          
        </li>
      
        <li class="list-item">
          
            <a href="/about/" class="item-link">About</a>
          
        </li>
      
    </ul>
    <div class="menu">
      <span class="icon-bar"></span>
      <span class="icon-bar"></span>
      <span class="icon-bar"></span>
    </div>
    <div class="menu-mask">
      <ul class="menu-list">
        
          <li class="menu-item">
            
              <a href="/" class="menu-link">Home</a>
            
          </li>
        
          <li class="menu-item">
            
              <a href="/tags/" class="menu-link">Tags</a>
            
          </li>
        
          <li class="menu-item">
            
              <a href="/archives/" class="menu-link">Archives</a>
            
          </li>
        
          <li class="menu-item">
            
              <a href="/project/" class="menu-link">Projects</a>
            
          </li>
        
          <li class="menu-item">
            
              <a href="/about/" class="menu-link">About</a>
            
          </li>
        
      </ul>
    </div>
  </div>
</header>

    <div id="article-banner">
  <h2>redis分布式缓存</h2>
  <p class="post-date">2019-05-24</p>
  <div class="arrow-down">
    <a href="javascript:;"></a>
  </div>
</div>
<main class="app-body flex-box">
  <!-- Article START -->
  <article class="post-article">
    <section class="markdown-content"><p>一.为什么选择 redis</p>
<p>在项目中使用 redis 做为缓存，还没有使用 memcache,考虑因素主要有两点：</p>
<p>1.redis 丰富的数据结构,其 hash,list,set 以及功能丰富的 String 的支持，对于实际项目中的使用有很大的帮忙。（可参考官网 redis.io）</p>
<p>2.redis 单点的性能也非常高效（利用项目中的数据测试优于 memcache）.</p>
<p>基于以上考虑，因此选用了 redis 来做为缓存应用。</p>
<p>二.分布式缓存的架构设计</p>
<p>1.架构设计</p>
<p>由于 redis 是单点，项目中需要使用，必须自己实现分布式。基本架构图如下所示：</p>
<p>2.分布式实现</p>
<p>通过 key 做一致性哈希，实现 key 对应 redis 结点的分布。</p>
<p>一致性哈希的实现：</p>
<p>l hash 值计算：通过支持 MD5 与 MurmurHash 两种计算方式，默认是采用 MurmurHash，高效的 hash 计算。</p>
<p>l 一致性的实现：通过 java 的 TreeMap 来模拟环状结构，实现均匀分布</p>
<p>3.client 的选择</p>
<p>对于 jedis 修改的主要是分区模块的修改，使其支持了跟据 BufferKey 进行分区，跟据不同的 redis 结点信息，可以初始化不同的 ShardInfo，同时也修改了 JedisPool 的底层实现，使其连接 pool 池支持跟据 key,value 的构造方法，跟据不同 ShardInfos，创建不同的 jedis 连接客户端，达到分区的效果，供应用层调用</p>
<p>4.模块的说明</p>
<p>l 脏数据处理模块，处理失败执行的缓存操作。</p>
<p>l 屏蔽监控模块，对于 jedis 操作的异常监控，当某结点出现异常可控制 redis 结点的切除等操作。</p>
<p>整个分布式模块通过 hornetq，来切除异常 redis 结点。对于新结点的增加，也可以通过 reload 方法实现增加。（此模块对于新增结点也可以很方便实现）</p>
<p>对于以上分布式架构的实现满足了项目的需求。另外使用中对于一些比较重要用途的缓存数据可以单独设置一些 redis 结点，设定特定的优先级。另外对于缓存接口的设计，也可以跟据需求，实现基本接口与一些特殊逻辑接口。对于 cas 相关操作，以及一些事物操作可以通过其 watch 机制来实现。（参考我以前写的 redis 事物介绍）</p>
<p>以上是基于 redis 分布式架构的介绍！但是应用中读写都是在一起的。相关写是在应用操作后 flush 或者 update 的，有一定的耦合。为了使读写分离，以及缓存模块跟应用的耦合更小，考虑使用 mysql binlog 来刷新缓存。以下是基于 binlog 刷新可性行分析以及实现过程中需要注意的地方。</p>
<p>三.采用 binlog 架构刷新缓存可行性分析</p>
<p>1.Mysql 日志格式介绍可参考我以前的的介绍。</p>
<p>2.对于使用 MIXED 日志格式，此日志格式，记录的是对应数据库操作的 SQL 语句，采用此日志方式存在的问题：</p>
<p>l 对于一些未任何更新操作的 SQl 语句，像条件不满足，对应的 sql 也会记录到 binlog 日志中。</p>
<p>l SQL 语句记录的未必包括所有的更新操作。</p>
<p>l 对于一些分布式数据库，对于 SQL 中的 where 条件指定的是非均衡字段，也许会存在多条 SQL,跟设计有关！</p>
<p>基于以上考虑，采用 MIXED 的日志格式进行 binlog 解析是行不通的。（官网给出的指示是 failed statementsare not logged ，但不包括语法没错误，更新条件不符合对应的 SQL）</p>
<p>3.采用 ROW 日志格式</p>
<p>对于此日志格式，每行变化都有对应的记录，此日志格式，对于解析及采集数据都是非常方便的，也只有采用此日志格式，才能基于 binlog 修改，做刷新缓存相关方案的设计。但是基于此日志格式也存在一些问题：</p>
<p>l 需要考虑项目中是否有大量的批量的 update 操作，如果采用此日志格式，批量操作每一行修改都会记录一条日志，大量的批量操作所产生的日志量，以及所带来的 IO 开销是否可以接受。</p>
<p>通过以上分析，最终项目中还是考虑基于 ROW 日志格式进行缓存刷新，还有一个问题需要考虑，在应用层 DB 进行了相应的 update 操作后，所产生的 Binlog 是会带来一定的延迟，如果 Binlog 处理模块正常运行，数据是的延迟会非常少，MS 级别以内，对用户体验是没有感知的，但是 Binlog 模块是多点，异常，以及相应的延迟肯定会是存在的，这样，缓存数据肯定会存在脏数据。</p>
<p>不过通过以上方案，数据能达到最终一致性，因此 how to 权衡，需要考虑。</p>
<p>通过以上分析，是否采用 Binlog 来做缓存数据刷新相信大家有一个基本概念了</p>
<p>四.基于 binlog 刷新缓存的实现时注意的地方</p>
<p>1.如果是采用 java 做相关开发，可以使用开源的 tungstenAPI</p>
<p>2.Binlog 日志解析是按照 mysql 的 master/slave 同步流程来实现，即一个线程同步，一个线程解析。</p>
<p>3.设计是可分 Binlog 处理模块以及缓存处理 SqlEvent 两部分，其中 Binlog 处理解析好对应的 SqlEvent，然后对应的缓存刷新处理 SqlEvent,一个简单的生产者-消费者模式。</p>
<p>4.对于多个 Binlog 处理模块可以是单点，也可以是通过一些协同工具来管理，看需求。可以使用 ZooKeeper 等。</p>
<p>5.对于分布式缓存中的数据，对于 Binlog 来刷新的缓存数据会存在 load 数据的问题，为了减轻 DB 的额外压力，flush 操作可在 get 缓存数据处完成。看需求，如果读写完全分享的话此 DB 的额外压力可以接收的话也可行。</p>
<p>6.对于缓存数据性一致性要求比较高的，可以通过版本号来控制，即在应用层引入一定的耦合，在 DB 操作时带 mark ，缓存刷新是也 mark，另外 get 操作时比较双版本号来达到数据的一致性。（此跟 5 谈论的一定的联系，读写是否完全分离，以及相应一致性实现的一些方法）</p>
</section>
    <!-- Tags START -->
    
    <div class="tags">
      <span>Tags:</span>
      
  <a href="/tags#分布式缓存">
    <span class="tag-code">分布式缓存</span>
  </a>

    </div>
    
    <!-- Tags END -->
    <!-- NAV START -->
    
  <div class="nav-container">
    <!-- reverse left and right to put prev and next in a more logic postition -->
    
      <a class="nav-left" href="/2019/05/24/多线程异步并发/BlockingQueue生产者消费者代码实现/">
        <span class="nav-arrow">← </span>
        
          BlockingQueue生产者消费者代码实现
        
      </a>
    
    
      <a class="nav-right" href="/2019/05/24/分布式消息队列MQ/RocketMQ安装部署测试/">
        
          RocketMQ安装部署测试
        
        <span class="nav-arrow"> →</span>
      </a>
    
  </div>

    <!-- NAV END -->
    <!-- 打赏 START -->
    
    <div class="money-like">
      <div class="reward-btn">
        赏
        <span class="money-code">
          <span class="alipay-code">
            <div class="code-image"></div>
            <b>使用支付宝打赏</b>
          </span>
          <span class="wechat-code">
            <div class="code-image"></div>
            <b>使用微信打赏</b>
          </span>
        </span>
      </div>
      <p class="notice">若你觉得我的文章对你有帮助，欢迎点击上方按钮对我打赏</p>
    </div>
    
    <!-- 打赏 END -->
    <!-- 二维码 START -->
    
    <div class="qrcode">
      <canvas id="share-qrcode"></canvas>
      <p class="notice">扫描二维码，分享此文章</p>
    </div>
    
    <!-- 二维码 END -->
    
    <!-- No Comment -->
    
  </article>
  <!-- Article END -->
  <!-- Catalog START -->
  
  <aside class="catalog-container">
  <div class="toc-main">
    <strong class="toc-title">Catalog</strong>
    
      <ol class="nav">none</ol>
    
  </div>
</aside>
  
  <!-- Catalog END -->
</main>

<script>
  (function () {
    var url = 'https://fengzhaoy.github.io/2019/05/24/分布式缓存/redis分布式缓存/';
    var banner = ''
    if (banner !== '' && banner !== 'undefined' && banner !== 'null') {
      $('#article-banner').css({
        'background-image': 'url(' + banner + ')'
      })
    } else {
      $('#article-banner').geopattern(url)
    }
    $('.header').removeClass('fixed-header')

    // error image
    $(".markdown-content img").on('error', function () {
      $(this).attr('src', 'http://file.muyutech.com/error-img.png')
      $(this).css({
        'cursor': 'default'
      })
    })

    // zoom image
    $(".markdown-content img").on('click', function () {
      var src = $(this).attr('src')
      if (src !== 'http://file.muyutech.com/error-img.png') {
        var imageW = $(this).width()
        var imageH = $(this).height()

        var zoom = ($(window).width() * 0.95 / imageW).toFixed(2)
        zoom = zoom < 1 ? 1 : zoom
        zoom = zoom > 2 ? 2 : zoom
        var transY = (($(window).height() - imageH) / 2).toFixed(2)

        $('body').append('<div class="image-view-wrap"><div class="image-view-inner"><img src="' + src +
          '" /></div></div>')
        $('.image-view-wrap').addClass('wrap-active')
        $('.image-view-wrap img').css({
          'width': `${imageW}`,
          'transform': `translate3d(0, ${transY}px, 0) scale3d(${zoom}, ${zoom}, 1)`
        })
        $('html').css('overflow', 'hidden')

        $('.image-view-wrap').on('click', function () {
          $(this).remove()
          $('html').attr('style', '')
        })
      }
    })
  })();
</script>


<script>
  var qr = new QRious({
    element: document.getElementById('share-qrcode'),
    value: document.location.href
  });
</script>





    <div class="scroll-top">
  <span class="arrow-icon"></span>
</div>
    <footer class="app-footer">
  <p class="copyright">
    &copy; 2019
  </p>
</footer>

<script>
  function async (u, c) {
    var d = document,
      t = 'script',
      o = d.createElement(t),
      s = d.getElementsByTagName(t)[0];
    o.src = u;
    if (c) {
      o.addEventListener('load', function (e) {
        c(null, e);
      }, false);
    }
    s.parentNode.insertBefore(o, s);
  }
</script>
<script>
  async ("//cdnjs.cloudflare.com/ajax/libs/fastclick/1.0.6/fastclick.min.js", function () {
    FastClick.attach(document.body);
  })
</script>

<script>
  var hasLine = 'true';
  async ("//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js", function () {
    $('figure pre').each(function (i, block) {
      var figure = $(this).parents('figure');
      if (hasLine === 'false') {
        figure.find('.gutter').hide();
      }
      var lang = figure.attr('class').split(' ')[1] || 'code';
      var codeHtml = $(this).html();
      var codeTag = document.createElement('code');
      codeTag.className = lang;
      codeTag.innerHTML = codeHtml;
      $(this).attr('class', '').empty().html(codeTag);
      figure.attr('data-lang', lang.toUpperCase());
      hljs.highlightBlock(block);
    });
  })
</script>
<!-- Baidu Tongji -->

<script src="/js/script.js"></script>
  </body>
</html>